/** @file    EncoderSQL.cpp
 *  @author  Alessandro Polo
 *  @version $Id: EncoderSQL.cpp 2047 2010-03-07 00:27:16Z alex $
 *  @brief
 * File containing methods for the wosh::database::EncoderSQL class.
 * The header for this class can be found in EncoderSQL.h, check that file
 * for class description.
 ****************************************************************************/
/* Copyright (c) 2007-2010, WOSH - Wide Open Smart Home 
 * by Alessandro Polo - OpenSmartHome.com
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the OpenSmartHome.com WOSH nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY Alessandro Polo ''AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL Alessandro Polo BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ****************************************************************************/

 #include <framework/database/EncoderSQL.h>

 #include <framework/database/EncoderSQLCommon.h>
 #include <core/Utilities.h>
 #include <core/SystemError.h>
 #include <core/FileSystem.h>
 #include <core/ObjectAllocator.h>

 #include <QFile>

 
using namespace std;
namespace wosh {
 namespace database {

 WOSH_REGISTER(wosh::database::EncoderSQL, "wosh::DataBase", 1.10, _static_EncoderSQL)

std::map<std::string, EncoderSQL_base*> EncoderSQL::objectRegs;
Mutex EncoderSQL::objRegsMux;

QSqlDatabase EncoderSQL::dbSQL;

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS

EncoderSQL::EncoderSQL() : DataBase()
 {
	Object::setName( _DataBaseSQL_NAME );

	Log.setContext( "EncoderSQL" );
	Log.setIndent( 3 );
	Log.setLevel( LOG_INFO );
Log.setLevel( LOG_VERBOSE );

	Log(LOG_INFO, " Loading SQL Driver {%s}..", QSqlDatabase::drivers().join(";").toLatin1().data() );

	dbSQL = QSqlDatabase::addDatabase( "QMYSQL" );
	//dbSQL = QSqlDatabase::addDatabase( "QSQLITE" );

	Log(LOG_INFO, " Registering Built-in Types.." );
	EncoderSQL_register_default_types();
	Log(LOG_VERBOSE, " Registered %i Built-in Types..", EncoderSQL::objectRegs.size() );

 }

EncoderSQL::~EncoderSQL()
 {
	Log(LOG_INFO, " Destroying.." );

	MutexLocker mL(&EncoderSQL::objRegsMux);
	map<string,EncoderSQL_base*>::iterator it;
	for ( it=EncoderSQL::objectRegs.begin(); it != EncoderSQL::objectRegs.end(); ++it ) {
		if ( it->second == NULL ) continue;
		delete it->second;
	}
	EncoderSQL::objectRegs.clear();

	if ( dbSQL.isOpen() ) {
		dbSQL.close();
	 }

	QStringList connections = QSqlDatabase::connectionNames();
	for(int i=0; i<connections.size(); i++)
		QSqlDatabase::removeDatabase( connections.at(i) );

	Log(LOG_VERBOSE, ":~EncoderSQL() Destroyed." );
 }

//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT EncoderSQL::registerDataType( const string& class_name, EncoderSQL_base* object )
 {
	if ( object == NULL ) return WRET_ERR_PARAM;

	MutexLocker mL(&EncoderSQL::objRegsMux);

	if ( EncoderSQL::objectRegs.find(class_name) != EncoderSQL::objectRegs.end() ) {
//		Log(LOG_WARNING, ":registerDataType() Overwriting datatype %s", class_name.c_str() );
		EncoderSQL_base* oldObj = EncoderSQL::objectRegs[class_name];
		if ( oldObj != NULL )
			delete oldObj;
		EncoderSQL::objectRegs[class_name] = NULL;
	 }

	EncoderSQL::objectRegs[ class_name ] = object;
//	Log(LOG_VERBOSE, ":registerDataType() Registered datatype %s", class_name.c_str() );

	return WRET_OK;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

EncoderSQL_base* EncoderSQL::findEncoderFor( const string& tagname )
 {
	if ( EncoderSQL::objectRegs.find( tagname ) == EncoderSQL::objectRegs.end() ) {
//		Log(LOG_CRITICAL, ":loadObjects() #%i FAILED Class not found '%s'", j, tagname.c_str() );
		return NULL;
	 }

	return EncoderSQL::objectRegs[ tagname ];
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT EncoderSQL::loadObjects( std::vector<ISerializable*>& objects, const string& class_name, const string& sql_source )
 {
	WRESULT conn = checkConnect();
	if ( WFAILED(conn) ) {
		return WRET_ERR_INTERNAL;
	 }

// USER:PASSWORD@ADDRESS:PORT/DATABASE.TABLE

	QString query_str = QString( ("SELECT * FROM `" + sql_source + "`").c_str() );
	QSqlQuery query(dbSQL);

	if ( !query.exec(query_str) ) {
		Log(LOG_CRITICAL, ":loadObjects() FAILED Invalid Query '%s' [%s]", sql_source.c_str(), query.lastError().text().toLatin1().data() );
		return WRET_ERR_PARAM;
	 }
	if ( !query.isSelect() ) {
		Log(LOG_WARNING, ":loadObjects() Query '%s' is NOT a SELECT", sql_source.c_str() );
	 }
	if ( query.size() <= 0 ) {
		Log(LOG_CRITICAL, ":loadObjects() FAILED : No items found in %s", sql_source.c_str() );
		return WRET_OK;
	 }

	Log(LOG_INFO, ":loadObjects() loading %i items in %s", query.size(), sql_source.c_str() );
	vector<string> classErrs;
	int j = 0;
    while( query.next() ) {
		++j;

		EncoderSQL_base* obj_ser = EncoderSQL::findEncoderFor(class_name);
		if ( obj_ser == NULL ) {
			classErrs.push_back(class_name);
			Log(LOG_CRITICAL, ":loadObjects() #%i FAILED Class not found '%s'", j, class_name.c_str() );
			continue;
		 }
		ISerializable* obj  = obj_ser->allocObject();

		WRESULT ret = obj_ser->deserializeFrom(obj, query);
		if ( WFAILED(ret) )
			Log(LOG_WARNING, ":loadObjects() #%i %s Deserialization-Error ret=%i", j, class_name.c_str(), ret );
		else
			Log(LOG_VERBOSE, ":loadObjects() #%i %s loaded.", j, class_name.c_str() );

		objects.push_back( obj );
	 }

	if ( !classErrs.empty() ) {
		string errs = Utilities::joinVector(classErrs, "; ");
		SystemError::raise(this, Log, SystemError::CRITICAL, 0, "loadObjects", "%i ERRORS: Classes not found: %s", classErrs.size(), errs.c_str() );
	 }

	return WRET_OK;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT EncoderSQL::saveObjects( std::vector<ISerializable*>& objects, const string& sql_target )
 {
	WRESULT conn = checkConnect();
	if ( WFAILED(conn) ) {
		return WRET_ERR_INTERNAL;
	 }

// USER:PASSWORD@ADDRESS:PORT/DATABASE/TABLE

	Log(LOG_VERBOSE, ":saveObjects() saving %i items in %s", objects.size(), sql_target.c_str() );

	QSqlQuery query;

	bool executed = false;
	vector<string> classErrs;
	int j = 0;
	std::vector<ISerializable*>::const_iterator it;
	for ( it=objects.begin(); it != objects.end(); ++it, ++j ) {
		if ( *it == NULL ) continue;
		
		if ( EncoderSQL::objectRegs.find( string((*it)->getClassName()) ) == EncoderSQL::objectRegs.end() ) {
			classErrs.push_back((*it)->getClassName());
			Log(LOG_CRITICAL, ":saveObjects() #%i Class not found '%s'", j, (*it)->getClassName() );
			continue;
		 }
		EncoderSQL_base* obj_ser = EncoderSQL::objectRegs[ string((*it)->getClassName()) ];

		WRESULT ret = obj_ser->serializeTo( *it, query );
		if ( WFAILED(ret) )
			Log(LOG_WARNING, ":saveObjects() #%i %s Error ret=%i", j, (*it)->getClassName(), ret );
		else
			Log(LOG_VERBOSE, ":saveObjects() #%i saved.", j );

		executed = query.exec();
		if ( !executed ) {
			Log(LOG_WARNING, ":saveObjects() #%i %s Query Error", j, (*it)->getClassName(), ret );
		 }
	 }

	if ( !classErrs.empty() ) {
		string errs = Utilities::joinVector(classErrs, "; ");
		SystemError::raise(this, Log, SystemError::CRITICAL, 0, "saveObjects", "%i ERRORS: Classes not found: %s", classErrs.size(), errs.c_str() );
	 }

	return WRET_OK;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT EncoderSQL::checkConnect()
 {
	Log(LOG_VERBOSE, ":checkConnect()" );

	if ( !dbSQL.isOpen() ) {
		dbSQL.setHostName("localhost");
		dbSQL.setDatabaseName("wosh");
		dbSQL.setUserName("root");
		dbSQL.setPassword("enjoySQLserver");
		bool ok = dbSQL.open();
		if ( !ok ) {
			SystemError::raise(this, Log, SystemError::CRITICAL, 0, "checkConnect", "Database Error [%s]", dbSQL.lastError().text().toLatin1().data() );
			return WRET_ERR_INTERNAL;
		 }
	 }
	if ( !dbSQL.isValid() ) {
		SystemError::raise(this, Log, SystemError::CRITICAL, 0, "checkConnect", "Database Error [%s]", dbSQL.lastError().text().toLatin1().data() );
		return WRET_ERR_INTERNAL;
	 }
	Log(LOG_INFO, ":checkConnect()" );

	return WRET_OK;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////


//////////////////////////////////////////////////////////////////////////////////////////////////////////////

 }; // namespace database
}; // namespace wosh
